﻿#define SVD

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Text;
using System.Threading;

using agree.composition;
using agree.schema;
using agree.mrs;

using alib;
using alib.Collections;
using alib.Debugging;
using alib.Enumerable;
using alib.String;
using alib.String.Builder;
using alib.Hashing;
using alib.Matrix;

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class SvdResult : identity_base
	{
		public SvdResult(IIdentity prv)
			: base(prv)
		{
		}
		public MrsAlignmentMatrix A { get { return (MrsAlignmentMatrix)Trace; } }

		public int K;
		public Double[] W;
		public Double[,] U;
		public Double[,] VT;
		public Double[,] B;
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class MrsAlignmentMatrix : identity_matrix
	{
		public MrsAlignmentMatrix(IIdentPair<Mrs> pr, StringIndex si, int c_feats, Variable[] vars, String[] col_hdrs, ElementRole[] er_all)
			: base(pr, c_feats + vars.Length, er_all.Length)
		{
			this.x = pr.x;
			this.y = pr.y;
			this.row_idx = si;
			this.c_feats = c_feats;
			this.vars = vars;
			this.col_hdrs = col_hdrs;
			this.er_all = er_all;

			load();
		}

		void load()
		{
			int i_row, i_col = 0;
			for (; i_col < c_cols; i_col++)
			{
				i_row = row_idx[er_all[i_col].role.name];
				base._values[i_row, i_col] = 1;
			}

			i_row = c_feats;
			foreach (var v in vars)
			{
				foreach (var eu in v.Usage)
					base._values[i_row, Array.IndexOf<ElementRole>(er_all, eu)] = 1;
				i_row++;
			}
		}

		readonly Mrs x, y;
		public Mrs MrsX { get { return x; } }
		public Mrs MrsY { get { return y; } }

		readonly StringIndex row_idx;
		public StringIndex RowIndex { get { return row_idx; } }

		String[] col_hdrs;
		public String[] ColumnHdrs { get { return col_hdrs; } }

		readonly int c_feats;
		public int FeatureRows { get { return c_feats; } }

		readonly Variable[] vars;
		public Variable[] Variables { get { return vars; } }

		readonly ElementRole[] er_all;
		public ElementRole[] ElementRoles { get { return er_all; } }

		public Grammar GrammarX
		{
			get { return (Grammar)P.Grammar.FindTracePropertyValue(MrsX); }
		}
		public Grammar GrammarY
		{
			get { return (Grammar)P.Grammar.FindTracePropertyValue(MrsY); }
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class PrepareAlignmentMatrix : m_scalar_base<IIdentPair<Mrs>, MrsAlignmentMatrix>// _functormonad_pam
	{
		public PrepareAlignmentMatrix(IRtParent parent)
			: base(parent)
		{
		}
		public PrepareAlignmentMatrix(IRtParent parent, IIdentPair<Mrs> pr)
			: base(parent, pr)
		{
		}

		protected override MrsAlignmentMatrix start(IIdentPair<Mrs> pr)
		{
			return build(pr, pr.x, pr.y);
		}

		MrsAlignmentMatrix build(IIdentPair<Mrs> pr, Mrs th, Mrs en)
		{
			var var_en = en.Variables.Where(v => v.Usage._Count() > 1).ToArray();
			var var_th = th.Variables.Where(v => v.Usage._Count() > 1).ToArray();

			var er_en = en.ElementRoles.ToArray();
			var er_th = th.ElementRoles.ToArray();
			var er_all = alib.Array.arr.Concat(er_en, er_th);

			var col_hdrs = new String[er_all.Length];

			for (int i = 0; i < er_all.Length; i++)
			{
				var er = er_all[i];
				var mrs = (mrs.Mrs)er.element.sem;
				var g = (Grammar)schema.P.Grammar.FindTracePropertyValue(mrs);
				col_hdrs[i] = String.Format("{0}:{1}", Char.ToLower(g.Name[0]), er.KeyName);
			}

			var row_idx = new StringIndex(29);

			foreach (var er in er_en)
				row_idx.Add(er.role.name);
			foreach (var er in er_th)
				row_idx.Add(er.role.name);

			int c_feats = row_idx.Count;

			RefList<Variable> vars = new RefList<Variable>();
			bool f_added;
			foreach (var v in var_en)
			{
				if (v.Usage._Count() > 1)
				{
					row_idx.Add(v.Name + "-en", out f_added);
					if (f_added)
						vars.Add(v);
				}
			}
			foreach (var v in var_th)
			{
				if (v.Usage._Count() > 1)
				{
					row_idx.Add(v.Name + "-th", out f_added);
					if (f_added)
						vars.Add(v);
				}
			}

			var a = new MrsAlignmentMatrix(pr, row_idx, c_feats, vars.GetTrimmed(), col_hdrs, er_all);

			return a;
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class SvdExecutor : m_scalar_base<MrsAlignmentMatrix, SvdResult>
	{
		public SvdExecutor(IRtParent prv, MrsAlignmentMatrix a)
			: base(prv, a)
		{
			LeftVectorsComputeOption = 2;
			RightVectorsComputeOption = 2;
			K = 5;
		}
		public SvdExecutor(IRtParent prv)
			: this(prv, default(MrsAlignmentMatrix))
		{
		}

		public int LeftVectorsComputeOption { get; set; }

		public int RightVectorsComputeOption { get; set; }

		public int K { get; set; }

		SvdResult result;

		protected override SvdResult start(MrsAlignmentMatrix a)
		{
			if (a == null)
				throw new ArgumentNullException();

			SvdResult _tmp;
			_svd_busy _blk;
			if ((_tmp = result) == null && (_tmp = Interlocked.CompareExchange(ref result, _blk = new _svd_busy(a), null)) == null)
			{
				_tmp = new SvdResult(a);
#if SVD
				//if (!alib.Svd.Svd.rmatrixsvd(a, c_rows, c_cols, 1/*0*/, 1, 2, ref w, ref _u, ref _vt))

				if (!alglib.svd.rmatrixsvd(
						a,				// identity trace
						a.RowCount,
						a.ColumnCount,
						LeftVectorsComputeOption,
						RightVectorsComputeOption,
						2,				/// memory option 
						ref _tmp.W,
						ref _tmp.U,
						ref _tmp.VT))
					throw new Exception("SVD probably non-converging; see 'maxitr'");

				_tmp.B = new Matrix(_tmp.W, K) * _tmp.VT.Trim(K, a.ColumnCount);

#endif
				result = _tmp;
				_blk.SetComplete();
			}
			else if ((_blk = _tmp as _svd_busy) != null)
				_tmp = _blk.Wait(ref result);

			return _tmp;
		}

		sealed class _svd_busy : SvdResult
		{
			public _svd_busy(IIdentity prv) : base(prv) { }
			ManualResetEvent mre;
			public void SetComplete()
			{
				var _tmp = Interlocked.Exchange(ref mre, null);
				if (_tmp != null)
					_tmp.Set();
			}
			public SvdResult Wait(ref SvdResult result)
			{
				ManualResetEvent _tmp;
				_tmp = mre ?? Interlocked.CompareExchange(ref mre, _tmp = new ManualResetEvent(false), null) ?? _tmp;
				if (result == this)
					_tmp.WaitOne();

				return result;
			}
		};
	};
}
